package com.baizhi.aspect;

import com.baizhi.annotation.LogAnnotation2;
import com.baizhi.dao.CmfzLogDao;
import com.baizhi.entity.CmfzAdmin;
import com.baizhi.entity.CmfzLog;
import com.baizhi.util.IPKit;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Date;

//定义一个aop类（切面类）  需要两个注解
@Aspect     //这个注解是指明当前类是一个aop类
@Configuration  //这个注解指明这是一个 配置类
public class LogAspect2 {
    @Autowired
    private CmfzLogDao cmfzLogDao;
    //当前类是一个关于日志的切面类  是对管理员操作各个模块的操作信息的记录统计
    //因为有很多个模块功能 所以将这个统计操作信息的功能抽取出来作为一个“额外功能”
    // 然后在合适的时机（切入点）“织入”到核心类（功能模块）中
    /*
    * 思路：首先，操作一个功能结束，我们记录这个动作  所以应该在核心类执行完才执行添加日志记录
    *               即此日志aop  应该设为 后置通知或环绕通知
    *       其次，在日志aop的方法内，我们增强功能（额外功能）就是添加日志信息到数据库，具体可分两步：
    *               第一、采集日志数据信息，将其封装到日志对象
    *               第二、执行添加数据库的操作
    *
    *       分析：最大的难点就在日志数据的采集上，那么分析如下
    *           日志表的字段，log_id  admin_id  log_date log_ip log_content  log_type
    *            log_id  日志编号，数据库中是自增字段，可以忽略
    *            admin_id  操作的管理员ID  这个需要用session获取  那么就需要获取session对象  这就需要request对象
    *                   在Spring的项目中有一个API  可以在项目的任意位置处获取request对象  所以此步可以解决
    *            log_date  操作的日期  即是代码执行的当下时间  直接new Date（）即可获取
    *            log_ip  操作所在的位置（计算机IP）   最简单的就是request.getRemoteAddr()获取一个远程IP
    *                       它也是需要一个request对象  上面已经获取   但一般企业都会自己封装一个工具类
    *                       来获取访问当前项目的IP  网上有很多资源
    *            log_content（操作的具体内容） 和 log_type（操作的类型）
    *            这两个字段都是很灵活 不好控制  不同的功能方法 类型不一样 内容也不同 如：
    *            type：查询   内容可以查询用户  查询上师  查询课程等等
    *            type：添加/删除/...   内容可以添加用户  删除上师  添加课程等等
    *            那么这个难点该如何解决：  这么灵活的数据   只能用自定义？？？来拿了
    *            解决方案：
    *            自定义一个注解，给注解声明两个属性，执行不同的操作时，自己在注解的属性中标明具体的操作内容和类型
    *            然后将获取的注解的属性值拿到  即是我们所需用的log_content和log_type
    *            大概步骤：
                1.先自定义注解（定义两个属性代表操作内容和操作类型），
                        注解用在要切入的方法（即管理员操作的功能方法）上（一般是Controller上）
                        所以这个日志aop类的切入点配置成了
                        注解切入方式@After("@annotation(com.baizhi.annotation.LogAnnotation)")
                2.在增强代码中获取被切入的方法上的注解
                    这里是利用JoinPoint对象 能获取到类 方法 注解 属性的特征实现的
                    所以要给该日志aop中的增强方法添加一个JoinPoint类型的形参
                3.通过JoinPoint形参获取注解的值
    * */
    //第一、定义一个自定义注解  声明content（）和type（）两个属性  这个实在annotation包中完成的
    //第二、在当前日志aop类中  写增强方法  这里写成后置通知  所以无需返回值
    /*
        这个@After注解  就是指明当前增强方法是个后置通知
        重点是这个里面的切入点配置  因为具体织入到哪个核心方法内 太多不好归纳  但是刚好所有要切入的方法
        都有使用我们的自定义注解，来标明操作类型和操作内容  所以我们就可以切入所有的自定义注解标注的方法
        这样就实现了：日志aop只切入添加了自定义注解的方法  就是管理员操作的每一个方法 就是我们想要的效果
    */
    @After("@annotation(com.baizhi.annotation.LogAnnotation2)")
    public void logAfterAop(JoinPoint joinPoint){//形参是对象  用来获取注解属性值的
        //准备一个日志对象  用来封装采集到的日志信息
        CmfzLog cmfzLog = new CmfzLog();
        //1、采集管理员编号admin_id
        //利用Spring中的API获取request对象  注意强转类型和导的包
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //获取session对象   获取登陆标识
        HttpSession session = request.getSession();
        CmfzAdmin cmfzAdmin = (CmfzAdmin) session.getAttribute("login");
        //逻辑判断
        if(cmfzAdmin!=null){
            cmfzLog.setAdminId(cmfzAdmin.getId());
        }else {
            cmfzLog.setAdminId(0);
        }
        //2、采集操作日期log_date
        cmfzLog.setLogDate(new Date());
        //3、采集操作地址log_ip   这里使用的是外部提供的工具类获取的  注意需要提供一个request对象
        String ip = IPKit.getIpAddrByRequest(request);
        cmfzLog.setLogIp(ip);
        //4、采集操作内容和操作类型  都是需要自定义注解获取的
        //首先，通过joinPoint对象 获取一个方法签名对象  注解类型强转和导包
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();//然后，通过方法签名对象 获取一个方法对象
        //再通过方法对象和自定义注解的类对象为形参  获取注解对象
        LogAnnotation2 annotation2 = method.getAnnotation(LogAnnotation2.class);
        //最后，通过主键对象 获取其属性值   注意：注解的属性的声明和用法
        String content = annotation2.content();
        String type = annotation2.type();
        //封装进日志对象
        cmfzLog.setLogContent(content);
        cmfzLog.setLogType(type);

        //第二大步、上面封装好日志对象  这里就可以调用添加sql的操作
        //          所以还需要给该日志aop类注入dao接口   装配dao接口的对象
        cmfzLogDao.insert(cmfzLog);
    }
}
